<?php
/**
 * Created by PhpStorm.
 * User: wangjie
 * Date: 2020/10/21
 * Time: 10:28
 */
namespace App\Api\Controllers\HwcPay;


use Illuminate\Support\Facades\Log;

class BaseController
{
    public $timeOut = 120;
    public $url = 'https://pay.hstypay.com/v2/pay/gateway'; //

    /**
     * 执行http调用
     * @param array $reqData 请求参数
     * @param string $private_rsa_key 私钥
     * @param string $public_rsa_key 公钥
     * @param string $signType 签名类型
     * @return array
     */
    public function call($reqData, $private_rsa_key, $public_rsa_key, $signType = 'RSA_1_256')
    {
        //启动一个CURL会话
        $ch = curl_init();
        $reqData['sign'] = $this->createSign($signType, $reqData, $private_rsa_key);
        $reqXmlData = $this->toXml($reqData);

        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeOut); // 设置curl允许执行的最长秒数
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //获取的信息以文件流的形式返回，而不是直接输出
        curl_setopt($ch, CURLOPT_POST, 1); //发送一个常规的POST请求
        curl_setopt($ch, CURLOPT_URL, $this->url);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $reqXmlData); //要传送的所有数据
        $res = curl_exec($ch);
        $responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if ($res == NULL) {
            $errInfo = "call http err :" . curl_errno($ch) . " - " . curl_error($ch) ;
            curl_close($ch);
            return [
                'status' => 2,
                'message' => $errInfo,
                'code' => $responseCode
            ];
        } elseif($responseCode  != "200") {
            $errInfo = "call http err httpcode=" . $responseCode;
            curl_close($ch);
            return [
                'status' => 2,
                'message' => $errInfo,
                'code' => $responseCode
            ];
        }

        curl_close($ch);
        $res_data = $this->setContent($res);
//        Log::info('汇旺财原始返回：');
//        Log::info($res_data);

        $status = $res_data['status']; //0表示成功，非0表示失败此字段是通信标识，非交易标识，交易是否成功需要查看 result_code 来判断
        if ($status == '0') {
            $result_code = $res_data['result_code']; //0表示成功，非0表示失败
            if ($result_code == '0') {
//                $sign = $res_data['sign'] ?? '';
//                $verifyRsa = $this->verifyRSASign($reqData, $sign, $public_rsa_key);
//                if (!$verifyRsa) {
//                    return [
//                        'status' => 2,
//                        'message' => '汇旺财支付，验签失败',
//                        'data' => $res_data
//                    ];
//                }

                return [
                    'status' => 1,
                    'message' => 'success',
                    'code' => $responseCode,
                    'data' => $res_data
                ];
            } else {
                return [
                    'status' => 2,
                    'message' => $res_data['err_msg'] ?? '汇旺财支付，交易失败',
                    'code' => $res_data['err_code'] ?? '',
                    'data' => $res_data
                ];
            }
        } else {
            return [
                'status' => 2,
                'message' => $res_data['message'] ?? '汇旺财支付，请求失败',
                'code' => $res_data['code'] ?? '',
                'data' => $res_data
            ];
        }

    }


    /**
     * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名
     * @param string $signType 签名类型
     * @param array $data 加签数据
     * @param string $private_rsa_key 加签私钥
     * @param string $getKey 加签key值
     * @return string
     */
    function createSign($signType = 'RSA_1_256', $data, $private_rsa_key, $getKey = '')
    {
        if ($signType == 'MD5') {
            return $this->createMD5Sign($data, $getKey);
        } else {
            return $this->createRSASign($data, $private_rsa_key, $signType);
        }
    }


    /**
     * md5加签
     * @param array $data 加签参数
     * @param string $getKey 加签key
     * @return string
     */
    function createMD5Sign($data, $getKey)
    {
        $signPars = "";
        ksort($data);
        foreach($data as $k => $v) {
            if("" != $v && "sign" != $k) {
                $signPars .= $k . "=" . $v . "&";
            }
        }
        $signPars .= "key=" . $getKey;
        $sign = strtoupper(md5($signPars));

        return $sign;
    }


    /**
     * RSA加签
     * @param array $data 加签数组
     * @param string $private_rsa_key 加签私钥
     * @param string $signType 加签类型
     * @return string
     */
    function createRSASign($data, $private_rsa_key, $signType = 'RSA_1_256')
    {
        $signPars = "";
        ksort($data);
        foreach($data as $k => $v) {
            if("" != $v && "sign" != $k) {
                $signPars .= $k . "=" . $v . "&";
            }
        }

        $signPars = substr($signPars, 0, strlen($signPars) - 1);

        $private_rsa_key = "-----BEGIN RSA PRIVATE KEY-----\n" .
            wordwrap($private_rsa_key, 64, "\n", true) .
            "\n-----END RSA PRIVATE KEY-----";

        $res = openssl_get_privatekey($private_rsa_key);
        if ($signType == 'RSA_1_1') {
            openssl_sign($signPars, $sign, $res);
        } elseif ($signType == 'RSA_1_256') {
            openssl_sign($signPars, $sign, $res, OPENSSL_ALGO_SHA256);
        }
        openssl_free_key($res);
        $sign = base64_encode($sign);

        return $sign;
    }


    /**
     * 将数据转为XML
     * @param array $array
     * @return string
     */
    public function toXml($array)
    {
        $xml = '<xml>';
        foreach($array as $k => $v) {
            $xml .= '<'.$k.'><![CDATA['.$v.']]></'.$k.'>';
        }

        $xml.='</xml>';

        return $xml;
    }


    /**
     *
     * @param $xmlSrc
     * @return array|bool
     */
    public function parseXML($xmlSrc)
    {
        if(empty($xmlSrc)){
            return false;
        }
        $array = array();
        libxml_disable_entity_loader(true);
        $xml = simplexml_load_string($xmlSrc);
        $encode = $this->getXmlEncode($xmlSrc);

        if($xml && $xml->children()) {
            foreach ($xml->children() as $node){
                //有子节点
                if($node->children()) {
                    $k = $node->getName();
                    $nodeXml = $node->asXML();
                    $v = substr($nodeXml, strlen($k)+2, strlen($nodeXml)-2*strlen($k)-5);

                } else {
                    $k = $node->getName();
                    $v = (string)$node;
                }

                if($encode!="" && $encode != "UTF-8") {
                    $k = iconv("UTF-8", $encode, $k);
                    $v = iconv("UTF-8", $encode, $v);
                }
                $array[$k] = $v;
            }
        }

        return $array;
    }


    /**
     * 获取xml编码
     * @param $xml
     * @return string
     */
    public function getXmlEncode($xml)
    {
        $ret = preg_match ("/<?xml[^>]* encoding=\"(.*)\"[^>]* ?>/i", $xml, $arr);
        if($ret) {
            return strtoupper($arr[1]);
        } else {
            return "";
        }
    }


    /**
     * 设置原始内容
     * @param $content
     * @return array
     */
    function setContent($content)
    {
        libxml_disable_entity_loader(true);
        $xml = simplexml_load_string($content);
        $encode = $this->getXmlEncode($content);

        $data = [];
        if($xml && $xml->children()) {
            foreach ($xml->children() as $node){
                //有子节点
                if ($node->children()) {
                    $k = $node->getName();
                    $nodeXml = $node->asXML();
                    $v = substr($nodeXml, strlen($k) + 2, strlen($nodeXml) - 2*strlen($k) - 5);
                } else {
                    $k = $node->getName();
                    $v = (string)$node;
                }

                if($encode != "" && $encode != "UTF-8") {
                    $k = iconv("UTF-8", $encode, $k);
                    $v = iconv("UTF-8", $encode, $v);
                }

                $data[$k] = $v;
            }
        }

        return $data;
    }


    /**
     * 是否平台签名,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
     * true:是
     * false:否
     * @param $sign
     * @param string $sign_type
     * @return bool
     */
    function isTenPaySign($sign, $sign_type = 'RSA_1_256')
    {
        $swiftPassSign = strtolower($sign);
        if ($sign_type == 'MD5') {
            return $this->getMD5Sign() == $swiftPassSign;
        } else if ($sign_type == 'RSA_1_1' || $sign_type == 'RSA_1_256') {
            return $this->verifyRSASign();
        }
    }


    /**
     * 获取数据MD5签名
     * @param array $reqData 加签数组
     * @param array $getKey 加签key值
     * @return string
     */
    function getMD5Sign($reqData, $getKey)
    {
        $signPars = "";
        ksort($reqData);
        foreach($reqData as $k => $v) {
            if("sign" != $k && "" != $v) {
                $signPars .= $k . "=" . $v . "&";
            }
        }
        $signPars .= "key=" . $getKey;

        return strtolower(md5($signPars));
    }


    /**
     * RSA验签
     * @param array $reqData 验签数据
     * @param string $sign 验证的签名
     * @param string $public_rsa_key 验签公钥
     * @param string $sign_type 验签类型
     * @return bool
     */
    function verifyRSASign($reqData, $sign, $public_rsa_key, $sign_type = 'RSA_1_256')
    {
        $signPars = "";
        ksort($reqData);
        foreach($reqData as $k => $v) {
            if("sign" != $k && "" != $v) {
                $signPars .= $k . "=" . $v . "&";
            }
        }

        $signPars = substr($signPars, 0, strlen($signPars) - 1);

        $public_rsa_key = "-----BEGIN PUBLIC KEY-----\n" .
            wordwrap($public_rsa_key, 64, "\n", true) .
            "\n-----END PUBLIC KEY-----";

        $res = openssl_get_publickey($public_rsa_key);
        if ($sign_type == 'RSA_1_1') {
            $result = (bool)openssl_verify($signPars, base64_decode($sign), $res);
            openssl_free_key($res);
            return $result;
        } else if($sign_type == 'RSA_1_256') {
            $result = (bool)openssl_verify($signPars, base64_decode($sign), $res, OPENSSL_ALGO_SHA256);
            openssl_free_key($res);
            return $result;
        }
    }


    /**
     * 32位随机数
     * @return string
     */
    public function nonceStr()
    {
        return md5(mt_rand(1000, 9999).time());
    }


    //统一 付款码支付 下单错误码
    public function getErrorMessageByPay($mes)
    {
        switch ($mes) {
            case 'SYSTEMERROR': return '接口返回错误,支付结果未知';
                break;
            case 'Internal error': return '接口返回错误,支付结果未知';
                break;
            case 'BANKERROR': return '银行系统异常,支付结果未知';
                break;
            case '10003': return '用户支付中，需要输入密码';
                break;
            case 'USERPAYING': return '用户支付中，需要输入密码';
                break;
            case 'System error': return '接口返回错误,支付结果未知';
                break;
            case 'aop.ACQ.SYSTEM_ERROR': return '接口返回错误,支付结果未知';
                break;
            case 'ACQ.SYSTEM_ERROR': return '接口返回错误,支付结果未知';
                break;
            case 'RULELIMIT': return '当前交易异常,支付确认失败';
                break;
            case 'TRADE_ERROR': return '103,暂无可用的支付方式,请绑定其它银行卡完成支付,支付确认失败';
                break;
            case 'PARAM_ERROR': return '参数错误,支付确认失败';
                break;
            case 'ORDERPAID': return '订单已支付,支付确认失败';
                break;
            case 'NOAUTH': return '商户无权限,支付确认失败';
                break;
            case 'AUTHCODEEXPIRE': return '二维码已过期，请用户在微信上刷新后再试;支付确认失败';
                break;
            case 'NOTENOUGH': return '余额不足,支付确认失败';
                break;
            case 'NOTSUPORTCARD': return '不支持卡类型,支付确认失败';
                break;
            case 'ORDERCLOSED': return '订单已关闭,支付确认失败';
                break;
            case 'ORDERREVERSED': return '订单已撤销,支付确认失败';
                break;
            case 'AUTH_CODE_ERROR': return '授权码参数错误,支付确认失败';
                break;
            case 'AUTH_CODE_INVALID': return '授权码检验错误,支付确认失败';
                break;
            case 'XML_FORMAT_ERROR': return 'XML格式错误,支付确认失败';
                break;
            case 'SIGNERROR': return '签名错误,支付确认失败';
                break;
            case 'LACK_PARAMS': return '缺少参数,支付确认失败';
                break;
            case 'NOT_UTF8': return '编码格式错误,支付确认失败';
                break;
            case 'BUYER_MISMATCH': return '支付帐号错误,支付确认失败';
                break;
            case 'APPID_NOT_EXIST': return 'APPID不存在,支付确认失败';
                break;
            case 'OUT_TRADE_NO_USED': return '商户订单号重复,支付确认失败';
                break;
            case 'APPID_MCHID_NOT_MATCH': return 'appid和mch_id不匹配,支付确认失败';
                break;
            case 'JMPT100027': return '付款码已扣款,支付确认失败';
                break;
            case 'ACQ.INVALID_PARAMETER': return '参数无效,支付确认失败';
                break;
            case 'ACQ.ACCESS_FORBIDDEN': return '无权限使用接口,支付确认失败';
                break;
            case 'ACQ.EXIST_FORBIDDEN_WORD': return '订单信息中包含违禁词,支付确认失败';
                break;
            case 'ACQ.TOTAL_FEE_EXCEED': return '订单总金额超过限额,支付确认失败';
                break;
            case 'ACQ.PAYMENT_AUTH_CODE_INVALID': return '支付授权码无效,支付确认失败,用户刷新条码后，重新扫码发起请求';
                break;
            case 'ACQ.CONTEXT_INCONSISTENT': return '交易信息被篡改;支付确认失败;更换商家订单号后，重新发起请求';
                break;
            case 'Auth code invalid': return '无效付款码;支付确认失败;每个二维码仅限使用一次，请刷新再试';
                break;
            case 'ACQ.BUYER_BALANCE_NOT_ENOUGH': return '买家余额不足;支付确认失败;买家绑定新的银行卡或者支付宝余额有钱后再发起支付';
                break;
            case 'ACQ.ERROR_BALANCE_PAYMENT_DISABLE': return '余额支付功能关闭;支付确认失败;用户打开余额支付开关后，再重新进行支付';
                break;
            case 'ACQ.BUYER_SELLER_EQUAL': return '买卖家不能相同;支付确认失败;更换买家重新付款';
                break;
            case 'ACQ.TRADE_BUYER_NOT_MATCH': return '交易买家不匹配;支付确认失败;更换商家订单号后，重新发起请求';
                break;
            case 'ACQ.BUYER_ENABLE_STATUS_FORBID': return '买家状态非法;支付确认失败;用户联系支付宝小二（联系支付宝文档右边的客服头像或到支持中心咨询），确认买家状态为什么非法';
                break;
            case 'ACQ.PULL_MOBILE_CASHIER_FAIL': return '唤起移动收银台失败;支付确认失败;用户刷新条码后，重新扫码发起请求';
                break;
            case 'ACQ.MOBILE_PAYMENT_SWITCH_OFF': return '用户的无线支付开关关闭;支付确认失败;用户在PC上打开无线支付开关后，再重新发起支付';
                break;
            case 'ACQ.PAYMENT_FAIL': return '支付失败;支付确认失败;用户刷新条码后，重新发起请求，如果重试一次后仍未成功，更换其它方式付款';
                break;
            case 'ACQ.BUYER_PAYMENT_AMOUNT_DAY_LIMIT_ERROR': return '买家付款日限额超限;支付确认失败;更换买家进行支付';
                break;
            case 'ACQ.BEYOND_PAY_RESTRICTION': return '商户收款额度超限;支付确认失败;联系支付宝提高限额';
                break;
            case 'ACQ.BEYOND_PER_RECEIPT_RESTRICTION': return '商户收款金额超过月限额;支付确认失败;联系支付宝提高限额';
                break;
            case 'ACQ.BUYER_PAYMENT_AMOUNT_MONTH_LIMIT_ERROR': return '买家付款月额度超限;支付确认失败;让买家更换账号后，重新付款或者更换其它付款方式';
                break;
            case 'ACQ.ERROR_BUYER_CERTIFY_LEVEL_LIMIT': return '买家未通过人行认证;支付确认失败;更换其它付款方式';
                break;
            case 'ACQ.PAYMENT_REQUEST_HAS_RISK': return '支付有风险;支付确认失败;更换其它付款方式';
                break;
            case 'ACQ.NO_PAYMENT_INSTRUMENTS_AVAILABLE': return '没用可用的支付工具;支付确认失败;更换其它付款方式';
                break;
            case 'ACQ.INVALID_STORE_ID': return '商户门店编号无效;支付确认失败;检查传入的门店编号是否有效';
                break;
            case 'Auto code invalid': return '无效付款码;支付确认失败;每个二维码仅限使用一次，请刷新再试';
                break;
            default: return $mes;
        }
    }


    //统一 付款码支付 查询错误码
    public function getErrorMessageByQuery($mes)
    {
        switch ($mes) {
            case 'ACQ.SYSTEM_ERROR': return '系统错误;后台系统返回错误;系统异常，重新发起查询';
                break;
            case 'aop.ACQ.SYSTEM_ERROR': return '接口返回错误;后台系统返回错误;请立即调用查询订单API，查询当前订单的状态，并根据订单状态决定下一步的操作';
                break;
            case 'SYSTEMERROR': return '系统错误;后台系统返回错误;系统异常，重新发起查询';
                break;
            case 'aop.unknow-error': return '系统繁忙;系统繁忙;系统繁忙，重新发起查询';
                break;
            case 'ACQ.INVALID_PARAMETER': return '参数无效;参数无效;不需要发起查询';
                break;
            case 'ACQ.TRADE_NOT_EXIST': return '查询的交易不存在;查询系统中不存在此交易订单号;不需要发起查询';
                break;
            case 'ORDERNOTEXIST': return '此交易订单号不存在;查询系统中不存在此交易订单号;不需要发起查询';
                break;
            case 'Order not exists': return '此交易订单号不存在;查询系统中不存在此交易订单号;不需要发起查询';
                break;
            case '105': return '数据不存在;此付款码已扣款，若买方还需付款，请让买方更新付款码;不需要发起查询';
                break;
            default: return $mes;
        }
    }


    //统一 付款码支付 退款错误码
    public function getErrMessageByReturn($mes)
    {
        switch ($mes) {
            case 'Refund exists': return '该退款请求已存在 ;商户退款单号重复;商户检查请求参数是否正确';
                break;
            case 'REFUNDINFOTEXIST': return '退款已存在';
                break;
            case 'REFUND_FEE_INVALID': return '退款金额错误';
                break;
            default: return $mes;
        }
    }


    //统一 扫码支付 透传第三方错误码
    public function getErrMessageByCode($mes)
    {
        switch ($mes) {
            case 'USERPAYING': return '用户支付中,需要输入密码;等待5秒,然后调用被扫订单结果查询当前订单的不同状态';
                break;
            case 'NOTENOUGH': return '用户余额不足,请用户换卡支付';
                break;
            case 'REFUND_FEE_INVALID': return '退款金额错误';
                break;
            case 'SYSTEMERROR': return '接口返回错误,请立即调用被扫订单结果查询当前订单状态';
                break;
            case 'ORDERREVERSED': return '订单已撤销,请用户重新支付';
                break;
            case 'ORDERISEXIST': return '订单已存在';
                break;
            case 'ORDERSTATUSERROR': return '订单状态异常';
                break;
            case 'ACCOUNTERROR': return '账户错误';
                break;
            case 'OUT_TRADE_NO_USED': return '商户订单号重复,请核实商户订单号是否重复提交';
                break;
            case 'AUTH_CODE_INVALID': return '授权码检验错误,请扫描微信支付被扫条码/二维码';
                break;
            case 'ACQ.TRADE_NOT_EXIST': return '支付宝,交易不存在';
                break;
            case 'ORDERNOTEXIST': return '订单不存在';
                break;
            case 'REFUNDINFOTEXIST': return '退款已存在';
                break;
            case 'ORDERPAID': return '订单已支付,请确认该订单号是否重复支付';
                break;
            default: return $mes;
        }
    }


    //统一 扫码支付 错误码
    public function getErrMessageByCodePay($mes)
    {
        switch ($mes) {
            case 'sub_appid and sub_openid not match': return '商户公众号appid和用户openid不匹配;如果是没有绑定，商户方提供正式的商户号和appid进行绑定；检查是否用对公众号appid和用户openid';
                break;
            case '受理机构必须传入sub_mch_id': return '受理机构必须传入sub_mch_id;提供所用商户号联系平台处理';
                break;
            case '受理关系不存在': return '受理关系不存在;提供所用商户号联系平台处理';
                break;
            case '消费超过该商户支持的限额': return '调用支付时金额超出商户支持的限额;金额传值时不要超过限额';
                break;
            case 'sub_openid is invalid': return 'sub_openid参数的值无效;仔细检查用户openID值,传入正确的值';
                break;
            case 'valid error': return '验证商户交易权限;检查商户配置是否正确';
                break;
            case 'transaction internal error': return '第三方接口调用;检查服务器网络/联系第三方是否有网络抖动';
                break;
            case 'unsupportapi': return '不支持的交易类型;验证上送的交易类型字段';
                break;
            case "valid mch'groupno fail": return '通过父级商户号和子商户号获取子商户;验证上送的父级商户号是否正确';
                break;
            case 'Internal error': return '支付网关返回状态非200;联系管理员排查';
                break;
            case 'valid sign fail': return '验证数据签名;商户检查密钥是否正确';
                break;
            case 'valid service fail': return '验证支付业务;检查商户配置是否正确';
                break;
            case 'valid pay gateway result no sign': return '返回结果失败;业务处理失败';
                break;
            case 'unsupported sign method': return '不支持的签名方法;检查sign_type';
                break;
            case 'total_fee:Must be less or equal to [5000000]': return '金额必须小于或等于5000000;检查total_fee';
                break;
            case 'Parse xml error,please use UTF-8 encoded': return 'xml参数解析出错,且编码格式必须utf-8;检查编码格式或xml格式';
                break;
            case 'xxxx:This field is required': return '文档必填参数没有传,xxxx表示文档中必填为是的参数;检查必填字段是否有传入或者必填参数名是否有误';
                break;
            case 'total_fee:Must be greater or equal to [1]': return '金额必须大于等于1;检查total_fee';
                break;
            case 'total_fee:Invalid': return '金额无效,金额单位是分,不能有小数点;检查total_fee';
                break;
            case 'Order paid': return '订单已支付';
                break;
            case 'Signature error': return '签名错误,参数签名结果不正确;仔细检查签名算法和密钥';
                break;
            case 'Auth valid fail': return '授权验证失败';
                break;
            case 'valid mch status fail': return '商户状态异常;商户被关停;与客服联系;检查商户配置是否正确';
                break;
            case 'refundInfo is exists': return '该退款请求已存在;	商户检查请求参数是否正确';
                break;
            case 'notify internal error[100590014546]': return '商户号100590014546通知异常,商户上送的通知地址不可达,商户检查网络状况';
                break;
            case 'valid param fail': return '请求参数不合法,商户请求参数格式不正确,商户检查请求参数是否正确';
                break;
            case 'valid flow fail': return '请求过于频繁,商户调用接口过于频繁,降低请求频率';
                break;
            case 'Order not exists': return '订单不存在,商户订单号不正确或者与所用订单号不是该商户号的';
                break;
            case '': return '';
                break;
            default: return $mes;
        }
    }


}
